re模块 -> 用于操作正则表达式

1. 常用的re模块方法

  • r -> 如果在正则前面或待匹配的字符串加上 r 就是防止字符串被转义(例如正则里面刚好有个 \\n 是要匹配 '\\n' 字符串的 此时 Python 就会将正则中 \\n 转义,那么在正则前面加上 r 就不会被转义了),所以最好在正则表达式前面都加上 r 以免会匹配错误,除非正则表达式里没有 \ 的特殊字符

  • re.findall('正则表达式', '待匹配的字符串', flags) -> 返回值: 列表 -> flags 可以不传

import re

ret = re.findall('a', 'eva egon yuan') 

print(ret) # ['a', 'a']

  • re.search('正则表达式', '待匹配的字符串', flags) -> 返回值: 对象 -> flags 可以不传 -> 函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用 .group() 方法得到匹配的字符串,如果字符串没有匹配,则返回None

import re

ret = re.search('a', 'eva egon yuan')  # <_sre.SRE_Match object; span=(2, 3), match='a'>

if ret:
    r = ret.group()  # a
    print(r)
else:
    print('匹配不到')

  • re.match('正则表达式', '待匹配的字符串', flags-> flags 可以不传 -> 匹配的是以xxx开头的字符串,若不是开头的,尽管属于str内,则无法匹配 -> 和 search 一样只会找到第一个匹配到得值然后返回一个对象,通过 .group() 获取结果

import re

ret = re.match('e', 'eva egon yuan')  # <_sre.SRE_Match object; span=(0, 1), match='e'>

if ret:
    r = ret.group()  # e
    print(r)
else:
    print('匹配不到')

  • re.split('正则表达式', '待匹配的字符串', flags-> flags 可以不传 -> 以匹配到的结果为分割线,将字符串分割成数组

import re

ret = re.split('\W+', 'hello,world')  # 匹配到的结果是 , 以 , 为分割线将字符串分割成数组

print(ret)  # ['hello', 'world']

  • re.sub('正则表达式', '需要替换的内容', '待匹配的字符串' , 替换的次数默认不传全部替换, flags-> flags 可以不传 -> 将匹配到的内容替换成指定的字符串 -> 返回值: 替换完的字符串

import re

ret1 = re.sub('\d', '被替换了', 'eva3egon4yuan4')

print(ret1)  # eva被替换了egon被替换了yuan被替换了

ret2 = re.sub('\d', '被替换了', 'eva3egon4yuan4', 1)

print(ret2)  # eva被替换了egon4yuan4

  • re.subn('正则表达式', '需要替换的内容', '待匹配的字符串' , 替换的次数默认不传全部替换, flags-> flags 可以不传 -> 将匹配到的内容替换成指定的字符串 -> 返回值: 元组 ('替换完的字符串',替换次数)

import re

ret = re.subn('\d', '被替换了', 'eva3egon4yuan4')

print(ret)  # ('eva被替换了egon被替换了yuan被替换了', 3)

  • re.finditer('正则表达式', '待匹配的字符串', flags) -> 返回值: 一个存放匹配结果的迭代器 -> flags 可以不传

import re

ret = re.finditer('\d', 'ds3sy4784a')  # <callable_iterator object at 0x0000025A9B978B70>

print(next(ret).group())  # 3
print(next(ret).group())  # 4
print(next(ret).group())  # 7

  • re.compile('正则表达式') -> 将正则表达式编译成一个正则表达式对象 -> 使用情况: 当一个正则表达式被重复使用的时候

import re

ro = re.compile('\d{3}')
ret = ro.search('abc123efg')  # <_sre.SRE_Match object; span=(3, 6), match='123'>

if ret:
    r = ret.group()
    print(r)  # 123
else:
    print('匹配不到')

import re

ro = re.compile('\d')
ret = ro.finditer('ds3sy4784a')  # <callable_iterator object at 0x0000025A9B978B70>

print(next(ret).group())  # 3
print(next(ret).group())  # 4
print(next(ret).group())  # 7

2. 优先级查询问题

  • re.search() 的优先级查询 -> 如果正则中有分组(),那么只需要在 .group(第几个分组) 传入第几个分组就可以获得该分组匹配到的内容

ret = re.search('^[1-9](\d{14})(\d{2}[0-9x])?$', '440981199703208810')

r = ret.group() # 440981199703208810
r2 = ret.group(1) # 40981199703208
r3 = ret.group(2) # 810

  • re.findall() 的优先级查询 -> findall会优先把分组匹配到的内容返回,如果想要匹配结果,取消权限即可 -> 取消权限: 在分组的第一个 ( 后面加上 ?:

import re

ret = re.findall(r'www\.(baidu|oldboy)\.com', 'www.oldboy.com')
print(ret)  # ['oldboy']

ret = re.findall(r'www\.(?:baidu|oldboy)\.com', 'www.oldboy.com')
print(ret)  # ['www.oldboy.com']

  • re.split() 的优先级查询 -> 没有加分组()不会保留所匹配到的内容而是作为分割线消失了,但是加了分组()就能够保留匹配到的内容,这个在某些需要保留匹配部分的使用过程是非常重要的。

ret = re.split("\d+", "eva3egon4yuan")
print(ret)  # ['eva', 'egon', 'yuan']

ret = re.split("(\d+)", "eva3egon4yuan")
print(ret)  # ['eva', '3', 'egon', '4', 'yuan']

3. 给分组()命名

  • 在优先级查询问题中我们可以看到如果正则有分组会优先取分组中的内容返回,此时我们可以通过给分组起名直接获取分组中匹配到的内容,而不需要通过下标 

  • 匹配到的结果通过 .group('name') 拿到对应的值

  • 自由返回值是对象并且对象中有.group()方法的re模块方法,才能通过分组的名字直接获取到对象的分组所匹配到的内容

    • re.search()
    • re.match()
    • re.finditer()

  • 匹配标签名

ret = re.search('<(?P<tag_name>\w+)>\w+</(?P=tag_name)>', '<h1>hello</h1>') # ?P=tag_name 表示使用名字为 tag_name 分组的匹配内容 ->  (?P=tag_name) 的匹配内容要和 (?P<tag_name>\w+) 的匹配内容要完全一致

if ret:
    r_tag_name = ret.group('tag_name')  # 匹配结果 h1

ret = re.search(r'<(\w+)>\w+</\1>', '<h1>hello</h1>') # \1 表示使用第一个分组的匹配内容,但是要在正则表达式前面加上 r 将他转为原生字符串

if ret:
    r_tag_name = ret.group(1)  # 匹配结果 h1

  • 匹配标签名和标签内容

ret_str = '''<h1>
    content1
</h1>
<h2>
    content2
</h2>
'''

ret = re.finditer('<(?P<tag_name>.*?)>(?P<tag_content>.*?)</(?P=tag_name)>', ret_str, re.S)

if ret:
    for r in ret:
        print(r.group('tag_name'))  # h1 h2
        print(r.group('tag_content').strip()) # content1 content2

4. flags 参数

  • re.S -> . 可以匹配任意字符,包括换行符 -> 最常用的

import re

string = """朝辞白帝彩云间
千里江陵一日还
两岸猿声啼不住
轻舟已过万重山
"""

# 使用 re.S
ret1 = re.findall('.*', string, re.S)
print(ret1)  # ['朝辞白帝彩云间\n千里江陵一日还\n两岸猿声啼不住\n轻舟已过万重山\n', '']

# 不使用 re.S
ret2 = re.findall('.*', string)
print(ret2)  # ['朝辞白帝彩云间', '', '千里江陵一日还', '', '两岸猿声啼不住', '', '轻舟已过万重山', '', '']

  • re.M -> 多行模式,改变^和$的行为(即: ^和$可以作用于每一行)

import re

string = """fall in love with you
i love you very much
i love she
i love her
"""

# 使用 re.M
ret1 = re.findall('^i.*', string, re.M)
print(ret1)  # ['i love you very much', 'i love she', 'i love her']

# 不使用 re.M
ret2 = re.findall('^i.*', string)
print(ret2)  # []

  • re.I -> 忽略大小写,括号内是完整的写法

  • re.L -> 做本地化识别的匹配,表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境,不推荐使用

  • re.U -> 使用\w \W \s \S \d \D使用取决于unicode定义的字符属性。在python3中默认使用该flag

  • re.X -> 冗长模式,该模式下pattern字符串可以是多行的,忽略空白字符,并可以添加注释

5. 爬虫例子

import json
import re
import zlib
from urllib.request import urlopen


def getPage(url):
    response = urlopen(url).read()
    response = zlib.decompress(response, 16 + zlib.MAX_WBITS)  # 解压网页
    return response.decode('utf-8', 'ignore')


def getData(html):
    html_data = re.compile(r'<div class="good-info".*?>.*?<div class="good-detail-text.*?<a .*?>(?P<title>.*?)</a>.*?<a .*?>(?P<sm_title>.*?)</a>.*?<span class="sale-price prime-cost">(?P<price>.*?)</span>', re.S)
    ret = html_data.finditer(html)
    for r in ret:
        yield {
            'title': r.group('title'),
            'sm_title': r.group('sm_title'),
            'price': r.group('price').strip(),
        }


def main(page):
    url = 'https://www.yohobuy.com/list/gd1.html?category_id=5,8&page=%s' % page
    html = getPage(url)
    data = getData(html)
    with open('yoho.txt', 'a', encoding='utf-8') as f:
        for d in data:
            print(d)
            f.write(json.dumps(d, ensure_ascii=False) + '\n')


for i in range(10):
    main(i + 1)

6. 计算器

import re


def dealwith(express):
# 对表达式中的符号进行处理 +- 替换成 - ,-- 替换成 +
    express = express.replace('+-', '-')
    express = express.replace('--', '+')
    return express


def cal_exp_son(exp_son):
# 用来计算原子型的表达式 两个数之间的乘除法
    if '/' in exp_son:
        a, b = exp_son.split('/')
        return str(float(a) / float(b))
    else:
        a, b = exp_son.split('*')
        return str(float(a) * float(b))


def cal_express_no_breacket(exp):
 # 计算没有括号的表达式
    # exp是没有经过处理的最内层带括号的表达式
    exp = exp.strip('()')
    while True:
        ret = re.search(r'\d+\.?\d*[*/]-?\d+\.?\d*', exp)  # 匹配第一个乘除
        if ret:  # 判断表达式中是否还有乘除
            exp_son = ret.group()
            print(exp_son)
            ret = cal_exp_son(exp_son)
            exp = exp.replace(exp_son, ret)
            exp = dealwith(exp)
        else:
            ret = re.findall(r'-?\d+\.?\d*', exp)
            sum = 0
            for i in ret:
                sum += float(i)
            return str(sum)


def remove_bracket(new_express):
# 提取括号里面没有其他括号的表达式
    while True:
        ret = re.search(r'\([^()]+\)', new_express)
        if ret:
            express_no_breacket = ret.group()
            print('匹配到内部不再有括号的值:', express_no_breacket)
            ret = cal_express_no_breacket(express_no_breacket)
            print(new_express, express_no_breacket, ret)
            new_express = new_express.replace(express_no_breacket, ret)
            new_express = dealwith(new_express)
            print(new_express)
        else:
            print('表达式中没有括号了:', new_express)
            ret = cal_express_no_breacket(new_express)
            print(ret)
            break


express = '1 - 2 * ( ( 6 0 -3 0  +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )'
new_express = express.replace(' ', '')
remove_bracket(new_express)